<?
    /* -*- Mode: PHP5; tab-width: 4; indent-tabs-mode: nil; basic-offset: 4 -*- */

    /*********************************************************
    *            --== InArch ==--
    *
    * LoginProcessor is the central authorization point of the
    * admin side of INA
    *
    * @author Kulikov Alexey <a.kulikov@gmail.com>
    * @version n/a
    * @since 21.04.2005
    * @copyright essentialmind.com 2005
    *
    *********************************************************/

    /***
    * Class loginProcessor
    *
    * Main purpose of this class is to authenticate the user
    * wishing to get access to the admin interface. Authentication
    * may be either manual (via an HTML form) or automatic (via
    * autologin cookie).
    *
    * @package Users
    * @author A Kulikov <a.kulikov@gmail.com>
    * @version n/a
    * @since 21.04.2005
    * @copyright essentialmind.com 2005
    *
    * @see http://developer.essentialmind.com/wiki/INA/docs/admin/procedures/login
    ***/
    class loginProcessor{
        private $db;        // reference to the database object
        private $smarty;    // reference to the smarty object
        private $log;       // reference to the log object

        /**
         * loginProcessor::__construct()
         *
         * @param ADOConnection $db - reference to the database object
         * @param mySmarty $smarty  - reference to the smarty object
         * @param object $log       - reference to the log object
         * @return void
         **/
        public function __construct(ADOConnection $db, mySmarty $smarty, $log){
            $this->db       = $db;
            $this->smarty   = $smarty;
            $this->log      = $log;
        }

        /**
         * loginProcessor::login()
         *
         * This function looks at any provided data inputs
         * (if any) and passes control to the data processor
         * which will try to authenticate the user. In case
         * the authentication went successul, then an admin
         * session will be cast. In all other cases the user
         * will be presented with a login form
         *
         * @return void
         **/
        public function login(){
            //first check if the user's IP was not blocked
            if($expires = $this->db->getOne("SELECT expires FROM user_blocked WHERE ip = ".(ip2long($_SERVER['REMOTE_ADDR'])?ip2long($_SERVER['REMOTE_ADDR']):'0')." AND expires > NOW()")){

                //log login attempt
                $this->log->recordAction("IP was denied access to the admin panel");

                //give parameters to smarty, such that a corresponding
                //template can be formed
                $this->smarty->assign("blocked",true);
                $this->smarty->assign("blockExpires",$expires);

            }else{  // no, we can continue ;)

                //was there a data submission?
                if($_POST or isset($_COOKIE['ina_autologin'])){
                    //this try block is to catch database errors!
                    try{

                        //POST request takes precedence over autologin cookies
                        //thus if no POST data was deliever, then we look
                        //for autologin data in the cookie
                        //
                        //please note, that autologin false login attempts are
                        //also counted
                        if(!$_POST){
                            //get parameters from cookie
                            $data = explode('%%',$_COOKIE['ina_autologin']);

                            //fake local vars
                            $_POST['username'] = $data[0];
                            $_POST['userpass'] = ((isset($data[1])) ? $data[1] : '');
                        }

                        //see if it matched
                        if($reply = $this->processPostData($_POST['username'],$_POST['userpass'])){ //yes
                            $this->castAdminSession($reply);

                        //no, it did not match
                        }else{
                            //make a note of a false login attempt
                            $this->log->recordAction("False login attempt with username: ".$_POST['username'].", attempt number: ".$_SESSION['falseAttempts']);

                            //generate error message
                            $this->smarty->assign("loginError",true);
                        }

                    //something went bad in the database =(
                    }catch(exception $e){
                        //log error
                        $this->log->recordError($e->getMessage(),$e->getCode());
                    }

                }// end if POST or COOKIE

            }// end if blocked

            // display stuff to the screen
            $this->smarty->display('login.html');

            // terminate script execution
            exit;
        }


        /**
         * loginProcessor::processPostData()
         *
         * This function will try to authenticate the provided
         * user:password pair against the database. In case
         * authentication is successfull, then the uid and the gid
         * returned to login(). Moreover, this function will
         * also keep track of falso login attempts
         *
         * @param string $userName
         * @param string $userPass
         * @return array
         **/
        private function processPostData($userName,$userPass){

            //check the database for a matching record
            //please note, that group id '1' is the system wide
            //default for the 'root' group having full access over
            //the system
            $userData = $this->db->getRow("SELECT
                                                user_access.id AS id,
                                                user_access.login AS login,
                                                user_in_group.gid AS gid,
                                                user_group.name AS group
                                            FROM user_access
                                            LEFT JOIN user_in_group ON user_access.id = user_in_group.uid
                                            LEFT JOIN user_group ON user_in_group.gid = user_group.id
                                            WHERE gid < 1000 AND login = '".$userName."' AND passwd = '".$userPass."'");

            /*
                Please take care to note, that multigroup support has not yet been implemented in the login Processor
                the database is ready
            */

            //is there a hit?
            if($userData){ //yes

                //check if there is an administrative block on that user
                if($expires = $this->db->getOne("SELECT expires FROM user_blocked WHERE uid = ".$userData['id']." AND expires > NOW()")){

                    //log user block
                    $this->log->recordAction("Blocked user with uid ".$userData['id']." attempted to login into the admin panel");

                    $this->smarty->assign("userBlockError",true);
                    $this->smarty->assign("userBlockExpires",$expires);
                    return false;

                }else{
                    //create autologin cookie?
                    if(isset($_POST['remember'])){
                        //log action
                        $this->log->recordAction("Autologin cookie set for user $userName");

                        //set autologin cookie
                        setcookie('ina_autologin',$userName.'%%'.$userPass,time()+60*60*24*14,'/');
                    }

                    /*
                        Please take care to note, that the code below is there out of performance reasons,
                        all the data is cached in the admin session and will not be verified again during
                        the session.
                    */
                    //retrieve access permissions for various parts of the control panel
                    $userData['permissions'] = $this->db->getAssoc("SELECT codeset, name FROM user_cp_access
                                                                    LEFT JOIN user_in_cp ON user_cp_access.id = user_in_cp.cpid
                                                                    LEFT JOIN user_group_in_cp ON user_cp_access.id = user_group_in_cp.cpid
                                                                    WHERE (uid = ".$userData['id']." OR gid = ".$userData['gid'].") AND istab = TRUE
                                                                    ORDER BY user_cp_access.id");

                    $userData['actions']     = $this->db->getAssoc("SELECT codeset, name FROM user_cp_access
                                                                    LEFT JOIN user_in_cp ON user_cp_access.id = user_in_cp.cpid
                                                                    LEFT JOIN user_group_in_cp ON user_cp_access.id = user_group_in_cp.cpid
                                                                    WHERE (uid = ".$userData['id']." OR gid = ".$userData['gid'].") AND istab = FALSE");

                    //return data
                    return $userData;
                }

            }else{ //no, the provided user login data is bogus

                //count the number of false attempts
                if(!isset($_SESSION['falseAttempts'])){
                    $_SESSION['falseAttempts'] = 1;
                }else{
                    $_SESSION['falseAttempts']++;
                }

                //see if the number of false attempts is greater
                //than the allowed maximum
                if($_SESSION['falseAttempts'] > 5){
                    //create database record
                    $this->db->Execute("INSERT INTO user_blocked(ip) VALUES(".ip2long($_SERVER['REMOTE_ADDR']).")");

                    //reset session
                    unset($_SESSION['falseAttempts']);

                    //log event
                    $this->log->recordAction("IP blocked due to numerous false login attempts");
                }

                //was than an autologin attempt? kill the cookie!
                if(isset($_COOKIE['ina_autologin'])){
                    setcookie('ina_autologin',false,time(),'/');
                }

                return false;
            }
        }


        /**
         * loginProcessor::castAdminSession()
         *
         * This function will cast the admin session and
         * redirect the user's browser to the admininistration
         * console
         *
         * @param array $data
         * @return void
         **/
        private function castAdminSession($data){
            //write session data
            $_SESSION['admin'] = $data;

            //do some garbage collection on the database
            // @2do, outsource this to a trigger
            try{
              $this->db->Execute("DELETE FROM user_blocked WHERE expires < NOW()");
            }catch(exception $e){
              //do nothing
            }

            //record action
            $this->log->recordAction("User \"".$data['login']."\" logged in into the admin panel!");

            //store session data
            session_commit();

            //redirect to internal page
            header("Location: ".$GLOBALS['_CONFIG']['admin_root']."sitestructure/");
            exit;
        }
    }
?>